summaryrefslogtreecommitdiff
path: root/app/api/auth/[...nextauth]/saml/provider.ts
diff options
context:
space:
mode:
Diffstat (limited to 'app/api/auth/[...nextauth]/saml/provider.ts')
-rw-r--r--app/api/auth/[...nextauth]/saml/provider.ts128
1 files changed, 128 insertions, 0 deletions
diff --git a/app/api/auth/[...nextauth]/saml/provider.ts b/app/api/auth/[...nextauth]/saml/provider.ts
new file mode 100644
index 00000000..92099be0
--- /dev/null
+++ b/app/api/auth/[...nextauth]/saml/provider.ts
@@ -0,0 +1,128 @@
+import CredentialsProvider from "next-auth/providers/credentials"
+import { getOrCreateSAMLUser, validateSAMLUserData } from '@/lib/users/saml-service'
+
+interface SAMLProviderOptions {
+ id: string
+ name: string
+ idp: {
+ sso_login_url: string
+ sso_logout_url: string
+ certificates: string[]
+ }
+ sp: {
+ entity_id: string
+ private_key: string
+ certificate: string
+ assert_endpoint: string
+ }
+}
+
+export function SAMLProvider(options: SAMLProviderOptions) {
+ return CredentialsProvider({
+ id: options.id,
+ name: options.name,
+ credentials: {
+ user: {
+ label: "User Data",
+ type: "text"
+ }
+ },
+ async authorize(credentials) {
+ try {
+ if (!credentials?.user) {
+ console.error('No user data provided')
+ return null
+ }
+
+ console.log('๐Ÿ” SAML Provider: Processing user data')
+
+ // ์‚ฌ์šฉ์ž ๋ฐ์ดํ„ฐ ํŒŒ์‹ฑ (UTF-8 ์ฒ˜๋ฆฌ ๊ฐœ์„ )
+ const userDataString = credentials.user
+ console.log('๐Ÿ”ค Raw user data string:', userDataString.substring(0, 200) + '...')
+
+ const userData = JSON.parse(userDataString)
+
+ // ํŒŒ์‹ฑ๋œ ๋ฐ์ดํ„ฐ์˜ UTF-8 ํ™•์ธ
+ console.log('๐Ÿ”ค Parsed user data UTF-8 check:', {
+ name: userData.name,
+ nameLength: userData.name?.length,
+ charCodes: userData.name ? [...userData.name].map(c => c.charCodeAt(0)) : []
+ })
+
+ if (!userData.id || !userData.email) {
+ console.error('Invalid SAML user data:', userData)
+ return null
+ }
+
+ console.log('โœ… SAML Provider: User authenticated successfully', {
+ id: userData.id,
+ email: userData.email,
+ name: userData.name
+ })
+
+ // ๐Ÿ”ฅ SAML ์‚ฌ์šฉ์ž ๋ฐ์ดํ„ฐ ๊ฒ€์ฆ
+ const isValidData = await validateSAMLUserData(userData)
+ if (!isValidData) {
+ console.error('Invalid SAML user data structure:', userData)
+ return null
+ }
+
+ // ๐Ÿ”ฅ JIT (Just-In-Time) ์‚ฌ์šฉ์ž ์ƒ์„ฑ ๋˜๋Š” ์กฐํšŒ
+ const dbUser = await getOrCreateSAMLUser({
+ email: userData.email,
+ name: userData.name,
+ // companyId: userData.companyId,
+ // techCompanyId: userData.techCompanyId,
+ // ! domain = evcp ์ด๋ฉด vendor๊ฐ€ ๊ฐ–๋Š” companyId, techCompanyId๋Š” null
+ companyId: undefined,
+ techCompanyId: undefined,
+ domain: userData.domain
+ })
+
+ if (!dbUser) {
+ console.error('Failed to get or create SAML user')
+ return null
+ }
+
+ // DB์—์„œ ๊ฐ€์ ธ์˜จ ์‹ค์ œ ์‚ฌ์šฉ์ž ์ •๋ณด ๋ฐ˜ํ™˜
+ const userResult = {
+ id: String(dbUser.id), // DB์˜ ์‹ค์ œ ID
+ name: dbUser.name, // DB์˜ ์‹ค์ œ ์ด๋ฆ„
+ email: dbUser.email, // DB์˜ ์‹ค์ œ ์ด๋ฉ”์ผ
+ companyId: dbUser.companyId, // DB์˜ ์‹ค์ œ ํšŒ์‚ฌ ID
+ techCompanyId: dbUser.techCompanyId, // DB์˜ ์‹ค์ œ ๊ธฐ์ˆ ํšŒ์‚ฌ ID
+ domain: dbUser.domain, // DB์˜ ์‹ค์ œ ๋„๋ฉ”์ธ
+ imageUrl: dbUser.imageUrl, // DB์˜ ์‹ค์ œ ์ด๋ฏธ์ง€ URL
+ }
+
+ console.log('โœ… SAML Provider: Returning user data to NextAuth:', userResult)
+ return userResult
+ } catch (error) {
+ console.error('โŒ SAML Provider: Authentication failed', error)
+ return null
+ }
+ }
+ })
+}
+
+// SAML ๋กœ๊ทธ์ธ URL ์ƒ์„ฑ ํ—ฌํผ ํ•จ์ˆ˜
+export function getSAMLLoginUrl(options: SAMLProviderOptions): string {
+ const params = new URLSearchParams({
+ SAMLRequest: 'placeholder', // ์‹ค์ œ๋กœ๋Š” createAuthnRequest()๋กœ ์ƒ์„ฑ
+ RelayState: options.sp.assert_endpoint,
+ })
+
+ return `${options.idp.sso_login_url}?${params.toString()}`
+}
+
+// SAML ์„ค์ • ๊ฒ€์ฆ
+export function validateSAMLOptions(options: SAMLProviderOptions): boolean {
+ const required = [
+ options.idp.sso_login_url,
+ options.sp.entity_id,
+ options.sp.assert_endpoint
+ ]
+
+ return required.every(field => field && field.length > 0)
+}
+ \ No newline at end of file